package org.aphelie.chronicle.ui.editors;

import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.aphelie.chronicle.provider.ChronicleItemProviderAdapterFactory;
import org.aphelie.chronicle.ui.ChronicleUIPlugin;
import org.aphelie.chronicle.ui.i18n.ChronicleUIMessages;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.ui.MarkerHelper;
import org.eclipse.emf.common.ui.editor.ProblemEditorPart;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.domain.IEditingDomainProvider;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory;
import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory;
import org.eclipse.emf.edit.ui.util.EditUIMarkerHelper;
import org.eclipse.emf.edit.ui.util.EditUIUtil;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabFolder;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.views.properties.PropertySheetPage;

public abstract class ArcFormEditor extends FormEditor implements
		IEditingDomainProvider {

	protected ComposedAdapterFactory adapterFactory;
	protected AdapterFactoryEditingDomain editingDomain;
	
    /**
     * Resources that have been removed since last activation.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    protected Collection<Resource> removedResources = new ArrayList<Resource>();

    /**
     * Resources that have been changed since last activation.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    protected Collection<Resource> changedResources = new ArrayList<Resource>();
    
	/**
	 * This keeps track of the active content viewer, which may be either one of the viewers in the pages or the content outline viewer.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	protected Viewer currentViewer;

    /**
     * Resources that have been saved.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    protected Collection<Resource> savedResources = new ArrayList<Resource>();

    /**
     * Map to store the diagnostic associated with a resource.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    protected Map<Resource, Diagnostic> resourceToDiagnosticMap = new LinkedHashMap<Resource, Diagnostic>();
    
	/**
	 * The MarkerHelper is responsible for creating workspace resource markers presented
	 * in Eclipse's Problems View.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	protected MarkerHelper markerHelper = new EditUIMarkerHelper();
	
	/**
	 * This is the property sheet page.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	protected List<PropertySheetPage> propertySheetPages = new ArrayList<PropertySheetPage>();

    /**
     * Controls whether the problem indication should be updated.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    protected boolean updateProblemIndication = true;
	
	protected ArcFormEditor() {
		initializeEditingDomain();
	}

	protected void initializeEditingDomain() {
		// Create an adapter factory that yields item providers.
		//
		adapterFactory = new ComposedAdapterFactory(ComposedAdapterFactory.Descriptor.Registry.INSTANCE);

		adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory());
		adapterFactory.addAdapterFactory(new ChronicleItemProviderAdapterFactory());
		adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory());

		// Create the command stack that will notify this editor as commands are executed.
		//
		BasicCommandStack commandStack = new BasicCommandStack();

		// Add a listener to set the most recent command's affected objects to be the selection of the viewer with focus.
		//
		commandStack.addCommandStackListener
			(new CommandStackListener() {
				 public void commandStackChanged(final EventObject event) {
					 getContainer().getDisplay().asyncExec
						 (new Runnable() {
							  public void run() {
								  firePropertyChange(IEditorPart.PROP_DIRTY);

								  // Try to select the affected objects.
								  //
								  Command mostRecentCommand = ((CommandStack)event.getSource()).getMostRecentCommand();
								  if (mostRecentCommand != null) {
									  setSelectionToViewer(mostRecentCommand.getAffectedObjects());
								  }
								  for (Iterator<PropertySheetPage> i = propertySheetPages.iterator(); i.hasNext(); ) {
									  PropertySheetPage propertySheetPage = i.next();
									  if (propertySheetPage.getControl().isDisposed()) {
										  i.remove();
									  }
									  else {
										  propertySheetPage.refresh();
									  }
								  }
							  }
						  });
				 }
			 });

		// Create the editing domain with a special command stack.
		//
		editingDomain = new AdapterFactoryEditingDomain(adapterFactory, commandStack, new HashMap<Resource, Boolean>());
	}

	public AdapterFactory getAdapterFactory() {
		return adapterFactory;
	}
	
	/**
	 * This sets the selection into whichever viewer is active.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	public void setSelectionToViewer(Collection<?> collection) {
		final Collection<?> theSelection = collection;
		// Make sure it's okay.
		//
		if (theSelection != null && !theSelection.isEmpty()) {
			Runnable runnable =
				new Runnable() {
					public void run() {
						// Try to select the items in the current content viewer of the editor.
						//
						if (currentViewer != null) {
							currentViewer.setSelection(new StructuredSelection(theSelection.toArray()), true);
						}
					}
				};
			getSite().getShell().getDisplay().asyncExec(runnable);
		}
	}

	@Override
	public EditingDomain getEditingDomain() {
		return editingDomain;
	}

	public Resource getResource() {
        URI resourceURI = EditUIUtil.getURI(getEditorInput());
        Resource resource = null;
        try {
                // Load the resource through the editing domain
                resource = editingDomain.getResourceSet().getResource(resourceURI, true);
        } catch (Exception e) {
                resource.getResourceSet().getResource(resourceURI, false);
        }
        return resource;
	}
	
    protected void setInput(IEditorInput editorInput) {
        super.setInput(editorInput);
        setPartName(getResource().getURI().lastSegment());
    }
	
    @Override
    public void doSave(IProgressMonitor progressMonitor) {
            // Save only resources that have actually changed.
            //
            final Map<Object, Object> saveOptions = new HashMap<Object, Object>();
            saveOptions.put(Resource.OPTION_SAVE_ONLY_IF_CHANGED, Resource.OPTION_SAVE_ONLY_IF_CHANGED_MEMORY_BUFFER);
            saveOptions.put(Resource.OPTION_LINE_DELIMITER, Resource.OPTION_LINE_DELIMITER_UNSPECIFIED);

            // Do the work within an operation because this is a long running activity that modifies the workbench.
            //
            WorkspaceModifyOperation operation =
                    new WorkspaceModifyOperation() {
                            // This is the method that gets invoked when the operation runs.
                            //
                            @Override
                            public void execute(IProgressMonitor monitor) {
                                    // Save the resources to the file system.
                                    //
                                    boolean first = true;
                                    for (Resource resource : editingDomain.getResourceSet().getResources()) {
                                            if ((first || !resource.getContents().isEmpty() || isPersisted(resource)) && !editingDomain.isReadOnly(resource)) {
                                                    try {
                                                            long timeStamp = resource.getTimeStamp();
                                                            resource.save(saveOptions);
                                                            if (resource.getTimeStamp() != timeStamp) {
                                                                    savedResources.add(resource);
                                                            }
                                                    }
                                                    catch (Exception exception) {
                                                            resourceToDiagnosticMap.put(resource, analyzeResourceProblems(resource, exception));
                                                    }
                                                    first = false;
                                            }
                                    }
                            }
                    };

            updateProblemIndication = false;
            try {
                    // This runs the options, and shows progress.
                    //
                    new ProgressMonitorDialog(getSite().getShell()).run(true, false, operation);

                    // Refresh the necessary state.
                    //
                    ((BasicCommandStack)editingDomain.getCommandStack()).saveIsDone();
                    firePropertyChange(IEditorPart.PROP_DIRTY);
            }
            catch (Exception exception) {
                    // Something went wrong that shouldn't.
                    //
                    ChronicleUIPlugin.getDefault().getLogger().log(exception);
            }
            updateProblemIndication = true;
            updateProblemIndication();
    }

	/**
	 * Returns a diagnostic describing the errors and warnings listed in the resource
	 * and the specified exception (if any).
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	public Diagnostic analyzeResourceProblems(Resource resource, Exception exception) {
		if (!resource.getErrors().isEmpty() || !resource.getWarnings().isEmpty()) {
			BasicDiagnostic basicDiagnostic =
				new BasicDiagnostic
					(Diagnostic.ERROR,
					 "org.aphelie.chronicle.editor",
					 0,
					 MessageFormat.format(ChronicleUIMessages.UI_CreateModelError_message, resource.getURI()),
					 new Object [] { exception == null ? (Object)resource : exception });
			basicDiagnostic.merge(EcoreUtil.computeDiagnostic(resource, true));
			return basicDiagnostic;
		}
		else if (exception != null) {
			return
				new BasicDiagnostic
					(Diagnostic.ERROR,
					 "org.aphelie.chronicle.editor",
					 0,
					 MessageFormat.format(ChronicleUIMessages.UI_CreateModelError_message, resource.getURI()),
					 new Object[] { exception });
		}
		else {
			return Diagnostic.OK_INSTANCE;
		}
	}

    
    /**
     * Updates the problems indication with the information described in the specified diagnostic.
     * <!-- begin-user-doc -->
     * <!-- end-user-doc -->
     * @generated
     */
    protected void updateProblemIndication() {
            if (updateProblemIndication) {
                    BasicDiagnostic diagnostic =
                            new BasicDiagnostic
                                    (Diagnostic.OK,
                                     "org.aphelie.romancer.chronicle.editor", //$NON-NLS-1$
                                     0,
                                     null,
                                     new Object [] { editingDomain.getResourceSet() });
                    for (Diagnostic childDiagnostic : resourceToDiagnosticMap.values()) {
                            if (childDiagnostic.getSeverity() != Diagnostic.OK) {
                                    diagnostic.add(childDiagnostic);
                            }
                    }

                    int lastEditorPage = getPageCount() - 1;
                    if (lastEditorPage >= 0 && getEditor(lastEditorPage) instanceof ProblemEditorPart) {
                            ((ProblemEditorPart)getEditor(lastEditorPage)).setDiagnostic(diagnostic);
                            if (diagnostic.getSeverity() != Diagnostic.OK) {
                                    setActivePage(lastEditorPage);
                            }
                    }
                    else if (diagnostic.getSeverity() != Diagnostic.OK) {
                            ProblemEditorPart problemEditorPart = new ProblemEditorPart();
                            problemEditorPart.setDiagnostic(diagnostic);
                            problemEditorPart.setMarkerHelper(markerHelper);
                            try {
                                    addPage(++lastEditorPage, problemEditorPart, getEditorInput());
                                    setPageText(lastEditorPage, problemEditorPart.getPartName());
                                    setActivePage(lastEditorPage);
                                    showTabs();
                            }
                            catch (PartInitException exception) {
                                    ChronicleUIPlugin.getDefault().getLogger().log(exception);
                            }
                    }

                    if (markerHelper.hasMarkers(editingDomain.getResourceSet())) {
                            markerHelper.deleteMarkers(editingDomain.getResourceSet());
                            if (diagnostic.getSeverity() != Diagnostic.OK) {
                                    try {
                                            markerHelper.createMarkers(diagnostic);
                                    }
                                    catch (CoreException exception) {
                                    	ChronicleUIPlugin.getDefault().getLogger().log(exception);
                                    }
                            }
                    }
            }
    }

	/**
	 * If there is more than one page in the multi-page editor part,
	 * this shows the tabs at the bottom.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	protected void showTabs() {
		if (getPageCount() > 1) {
			setPageText(0, ChronicleUIMessages.UI_SelectionPage);
			if (getContainer() instanceof CTabFolder) {
				((CTabFolder)getContainer()).setTabHeight(SWT.DEFAULT);
				Point point = getContainer().getSize();
				getContainer().setSize(point.x, point.y - 6);
			}
		}
	}
	
	/**
	 * This returns whether something has been persisted to the URI of the specified resource.
	 * The implementation uses the URI converter from the editor's resource set to try to open an input stream.
	 * <!-- begin-user-doc -->
	 * <!-- end-user-doc -->
	 * @generated
	 */
	protected boolean isPersisted(Resource resource) {
		boolean result = false;
		try {
			InputStream stream = editingDomain.getResourceSet().getURIConverter().createInputStream(resource.getURI());
			if (stream != null) {
				result = true;
				stream.close();
			}
		}
		catch (IOException e) {
			// Ignore
		}
		return result;
	}

    
}
